﻿using System.Globalization;
using Newtonsoft.Json.Linq;

namespace BlogsAPI.Sites
{
    using System.Collections.Generic;
    using System.Threading;
    using System.Net;
    using System;
    using System.Text.RegularExpressions;
    using Abstract;
    using Interfaces;
    using System.IO;
    using System.Web;
    using System.Diagnostics;

    public struct QipAccaunt
    {
        public int id;
        public string avatar;
        public string login;
        public string name;
        public string gender;
    }


    /// <summary>
    /// Microblogging at QIP portal
    /// </summary>
    /// <seealso cref="http://mblogi.qip.ru/api/gate"/>
    /// <remarks>If server fails to process ur request it redirects you to http://qip.ru/index/503 =(</remarks>
    /// <example>Created accaunt UserAccount("blogsapi","blogsapi") for tests</example>
    [BlogServiceAPI(Description = "http://mblogi.qip.ru", IsMicroblog = true)]
    [Obsolete]
    public class Qip : BlogServer, IPosts, IFriends
    {
        private List<QipAccaunt> FriendList;
        private String _SID;
        private static readonly Regex Validation = new Regex(@"^http://mblogi\.qip\.ru", Regexoptions);
        private static readonly Regex ValidationFull = new Regex(@"^http://mblogi\.qip\.ru/(?:people/(.*?)(?:/|$))?", Regexoptions);
        
        private static readonly Regex rObject = new Regex(@"(?:<lj\-embed>\s*)?(<object\s.*?</object>)(?:\s*</lj\-embed>)?", Regexoptions);

        /// <summary>
        /// Идентификатор сессии
        /// </summary>
        private String SID
        {
            get
            {
                return _SID;
            }
            set
            {
                FriendList = null;
                _SID = value;
            }
        }

        #region interface: BlogServer

        /// <summary>
        /// Конструктор!
        /// </summary>
        [DebuggerStepThrough]
        public Qip()
        {
            SID = String.Empty;
        }

        /// <summary>
        /// проверка, является ли передаваемый урл чатью этого сервиса
        /// </summary>
        public override Boolean ValidateLink(String sLink)
        {
            return Validation.Match(sLink).Success;
        }


        /// <summary>
        /// Получение ссылки на блог блогера
        /// </summary>
        /// <param name="userName">ник блогера</param>
        public override String GetBlogLink(string userName)
        {
            return String.Format("http://mblogi.qip.ru/people/{0}", userName);
        }

        /// <summary>
        /// проверка, является ли передаваемый урл чатью этого сервиса
        /// </summary>
        /// <param name="sLink">ссылка для проверки</param>
        /// <param name="userName">Имя пользователя на блогохостинге</param>
        /// <param name="bType">Тип блога - сообщество или блог</param>
        /// <remarks>http://mblogi.qip.ru/msg/92549578</remarks>
        public override Boolean ValidateLink(String sLink, out String userName, out BlogType bType)
        {
            bType = BlogType.Unknown;
            userName = String.Empty;
            Match m = ValidationFull.Match(sLink);
            if (m.Success)
            {
                bType = BlogType.User;
                userName = m.Groups[1].ToString();
            }
            return m.Success;
        }

        /// <summary>
        /// Получение ссылки на блог блогера
        /// </summary>
        /// <param name="userName">ник блогера</param>
        /// <param name="btype">Тип блога - сообщество или блог</param>
        public override String GetBlogLink(string userName, BlogType btype)
        {
            return GetBlogLink(userName);
        }

        /// <summary>
        /// Получение ссылки на RSS блогера - берем старым способом... парся страницу целиком
        /// </summary>
        /// <param name="UserName">ник блогера</param>
        /// <param name="btype"></param>
        /// <remarks>Неработает если имя пользователя русское, тогда нужен номер</remarks>
        public override String GetRssLink(string UserName, BlogType btype)
        {
            return String.Format("http://mblogi.qip.ru/user/{0}/rss", UserName);
        }

        #endregion

        /// <summary>
        /// Формирование строчки с GET параметрами
        /// </summary>
        /// <param name="FunName">название функции</param>
        /// <param name="parameters">массив параметров</param>
        /// <seealso cref="http://mblogi.qip.ru/api/gate"/>
        /// <returns>URI для запроса</returns>
        private string FormApiParams(string FunName, params string[] parameters)
        {
            if (parameters.Length % 2 != 0) throw new ArgumentOutOfRangeException("parameters");
            string pm = String.Empty;
            for (int i = 0; i < parameters.Length; i += 2)
            {
                pm += String.Format("\"{0}\":\"{1}\",", parameters[i], parameters[i + 1]);
            }
            return String.Format("http://mblogi.qip.ru/api/recv?method=mblogi.{0}&params={1}",
                                 FunName, HttpUtility.UrlEncode("{" + pm.Trim(',') + "}"));
        }

        /// <summary>
        /// Вызов функции из API
        /// </summary>
        /// <param name="FunName">название функции</param>
        /// <param name="parameters">массив параметров</param>
        /// <seealso cref="http://mblogi.qip.ru/api/gate"/>
        /// <returns>неразобранный объект ответа в виде строки</returns>
        private string CallApiFunction(string FunName, params string[] parameters)
        {
            string sRet = String.Empty;
            try
            {
                HttpWebRequest myHttpWebRequest = MakeRequest(FormApiParams(FunName, parameters));
                myHttpWebRequest.Method = "GET"; // метод GET
                myHttpWebRequest.Referer = "http://mblogi.qip.ru/api/";
                //получаем ответ и его разбираем...
                HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
                sRet = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding).ReadToEnd();
                sRet = sRet.Replace("\\/", "/");//coz of JS crap inside
                myHttpWebResponse.Close();
            }
            catch (Exception e1)
            {
                Debug.WriteLine(String.Format("Qip:FunctionCall = {0}:{1}", FunName, e1.Message));
            }
            return sRet;

        }

        /// <summary>
        /// Извлечение html кода Img аватарки для блога.
        /// </summary>
        /// <param name="userName">ник блогера</param>
        /// <param name="btype">Тип блога - сообщество или блог</param>
        /// <returns></returns>
        public override string ExtractAvatar(string userName, BlogType btype)
        {
            String sRet = String.Empty;
            try
            {
                string oRet = CallApiFunction("info.show", "or user", userName);
                QipAccaunt acc = ParseString(Regex.Match(oRet, @"{([^{}]+)}", Regexoptions).Groups[1].Value);
                if (!String.IsNullOrEmpty(acc.avatar))
                {
                    sRet = Common.FixHttp(acc.avatar.TrimStart('/'));
                }
            }
            catch (Exception e1)
            {
                Debug.WriteLine("Ошибка загрузки аватарки блога: " + e1.Message);
            }
            return sRet;
        }

        #region interface: IPosts

        /// <summary>
        /// Получение списка последних записей
        /// </summary>
        /// <param name="count">число записей</param>
        /// <returns>массив постов для индексирования</returns>
        public List<BlogPost> GetLatest(int count = 20, bool onlyrussian = true)
        {
            var oRet = new List<BlogPost>(0);
            try
            {
                string sJS = CallApiFunction("timeline.publish", 
                    new []{"count",count.ToString()});
                JObject data = JObject.Parse(sJS);
                //http://mblogi.qip.ru/msg/114941146
                //http://mblogi.qip.ru/people/jokeravtofan
                //msg_id , dt, "answer_count", "body", "login", 
                foreach (KeyValuePair<string,JToken> jMsg in data)
                {
                    string body = jMsg.Value["body"].Value<string>();
                    if (!String.IsNullOrEmpty(body) && Common.IsRussian(body.Replace("ссылка","")))
                    {
                        BlogPost bp = BlogPost.SimpleLink(String.Format("http://mblogi.qip.ru/msg/{0}", jMsg.Key));
                        bp.Comments = 0;
                        bp.Links = 0;
                        bp.SpecialParam = jMsg.Value["login"].Value<string>();
                        bp.Body = body;
                        bp.Subject = jMsg.Value["name"].Value<string>();
                        if (!DateTime.TryParse(jMsg.Value["dt"].Value<string>().Replace(" ", "T"),
                                               new CultureInfo("en-US"), DateTimeStyles.AssumeLocal, out bp.Published))
                        {
                            bp.Published = DateTime.Now;
                        }
                        oRet.Add(bp);
                    }
                }
            }catch(Exception e1)
            {
                Debug.Write(e1);
            }
            return oRet;
        }

        /// <summary>
        /// Обрабатываем пост в соответствии с требованиями блогохостинга
        /// перед его публикацией куда-нить.
        /// </summary>
        /// <param name="bp"></param>
        /// <returns></returns>
        /// <remarks>в Наши задачи входит окаймить object тэгом <lj-embed></remarks>
        public override BlogPost PreparePost(BlogPost bp)
        {
            bp.Body = rObject.Replace(bp.Body, "<lj-embed>$1</lj-embed>");
            return base.PreparePost(bp);
        }

        /// <summary>
        /// Залогинивание пользователя
        /// </summary>
        /// <param name="user">UserAccount</param>
        /// <returns></returns>
        /// <remarks>http://mblogi.qip.ru/api/gate</remarks>
        public override bool LogInto(UserAccount user)
        {

            string oRet;
            //если уже залогинены и есть СИД, то проверим его 
            if (_isLoggedIn && !String.IsNullOrEmpty(SID))
            {
                oRet = CallApiFunction("auth.checksid", "Sid", SID);
                if (oRet.Contains("true")) return true;
                //иначе наша сессия истекла
                _isLoggedIn = false;
                SID = String.Empty;
            }

            oRet = CallApiFunction("auth.getsid", "user", user.username, "password", user.password);
            if (oRet.Contains("\"Error\"") || !oRet.Contains(":"))
            {
                Debug.Write("Qip:LogInto = " + oRet);
                return false;
            }
            //SID = UseAPI%3D66ad52206e25cda96239329d56668c19
            SID = HttpUtility.UrlEncode(oRet.Trim(new[] { '{', '}', '"' }).Split(':')[1].Trim('"'));
            ///
            _isLoggedIn = !String.IsNullOrEmpty(SID);
            if (_isLoggedIn)
            {
                LoggedInAs = user;
            }
            return _isLoggedIn;
        }

        /// <summary>
        /// Выкладывание записи в блоге
        /// </summary>
        /// <param name="user">Аккаунт пользователя, который делает пост</param>
        /// <param name="bp">пост для блога</param>
        /// <returns></returns>
        public override PostResult MakePost(UserAccount user, BlogPost bp)
        {
            PostResult ps = new PostResult { Error = new NotImplementedException() };
            try
            {
                if (String.IsNullOrEmpty(bp.Body))
                {
                    throw new ArgumentNullException("bp");
                }
                //при залогинивании заодно определиться номер пользователя
                if (!LogInto(user))
                {
                    throw new Exception("Qip: не удалось залогинится");
                }
                //{"Sid":"UseAPI%3Dc5b72756b4df00ac1ebf761c42951633","MsgID":"","Body":"","partner_name":"","partner_key":"","partner_jid":""}
                String oRet = CallApiFunction("message.update", "Sid", SID, "Body",
                                              HttpUtility.UrlEncode(bp.Body.Length > 140
                                                                        ? bp.Body.Substring(0, 140)
                                                                        : bp.Body).Replace("+", "%20"));
                if (oRet.Contains("\"Error\"") || !oRet.Contains("http://")) throw new Exception(oRet);
                ps.Error = null;
                bp.Link = new Uri(Regex.Match(oRet, @"http://.*?(?:$|"")").Groups[0].Value);
                bp.Existance = true;
            }
            catch (Exception we)
            {
                Log("Qip:MakePost WebError:" + we.Message);
                ps.Error = we;
            }
            //
            return ps;
        }

        #region interface: OLD-IPosts
        /*
        /// <summary>
        /// Выкладывание записи в блоге
        /// </summary>
        /// <param name="user">Аккаунт пользователя, который делает пост</param>
        /// <param name="bp">пост для блога</param>
        /// <returns></returns>
        public override PostResult MakePost(UserAccount user, BlogPost bp)
        {
            PostResult ps = new PostResult { Error = new NotImplementedException() };
            try
            {
                if(String.IsNullOrEmpty(bp.Body))
                {
                    throw new ArgumentNullException("bp");
                }
                //при залогинивании заодно определиться номер пользователя
                if (!LogInto(user))
                {
                    throw new Exception("Qip: не удалось залогинится");
                }
                //making post here
                HttpWebRequest myHttpWebRequest = MakeRequest("http://mblogi.qip.ru/post/msg");
                myHttpWebRequest.Method = "POST";
                myHttpWebRequest.Referer = GetBlogLink(user.username,BlogType.User);
                myHttpWebRequest.ContentType = "application/x-www-form-urlencoded";
                // передаем параметры
                string sQueryString = String.Format("message={0}",
                    HttpUtility.UrlEncode(bp.Body.Length>140?bp.Body.Substring(0,140):bp.Body));
                byte[] byteArr = _encoding.GetBytes(sQueryString);
                myHttpWebRequest.ContentLength = byteArr.Length;
                myHttpWebRequest.GetRequestStream().Write(byteArr, 0, byteArr.Length);
                HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
                //получаем ответ
                //string result = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding).ReadToEnd();
                myHttpWebResponse.Close();
                if(myHttpWebResponse.StatusCode == HttpStatusCode.OK)
                {
                    //----------
                    ps.Error = null;
                    //---------
                    Log("Не удалось найти ссылку на опубликованный пост. Вытаскиваем его из RSS.");
                    RssDocument rDoc =
                    RssDocument.Load(new Uri(GetRssLink(user.username, BlogType.User)));
                    //берем самый последний пост и радуемся...
                    if (rDoc.Channel.Items.Count > 0)
                    {
                        ps.sLink = new Uri(rDoc.Channel.Items[0].Link);
                    }
                }
                bp.Existance = true;
            }
            catch (Exception we)
            {
                Log("MakePost WebError: " + we.Message);
                ps.Error = we;
            }
            //
            return ps;
        }

        /// <summary>
        /// Залогинивание пользователя
        /// Однако для постинга-комментинга она не используется
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        /// <remarks>
        /// URL=http://api.qip.ru/api/test?r=0.039200626707121766&user=topbot&pass=qwe00qwe&alien=&time=-180&JsHttpRequest=12646853565971-script
        /// Referer=http://mblogi.qip.ru/people/topbot
        /// 
        /// Set-Cookie=autologin=%7B%22login%22%3A%22topbot%40qip.ru%22%2C%22hash%22%3A%22bb36ba6425937189881845e1814bb9c1%22%7D; expires=Sat, 27-Feb-2010 13:29:19 GMT; path=/; domain=qip.ru
        /// PHPSESSID=d60bb21311047ceb9a5a423ad4c83fa7; path=/; domain=qip.ru
        /// startqip_uniq=e81b4257ad91b6f5f333e1a6f009d0de; expires=Sat, 27-Feb-2010 13:29:19 GMT; path=/; domain=qip.ru
        /// </remarks>
        public override bool LogInto(UserAccount user)
        {
            
            if (_isLogedIn) return true;
            HttpWebRequest myHttpWebRequest = MakeRequest(String.Format("http://api.qip.ru/api/test?r=0.039200626707121766&user={0}&pass={1}&alien=&time=-180&JsHttpRequest={2}-script",
                user.username,user.password,DateTime.Now.Ticks));
            myHttpWebRequest.Method = "GET";
            myHttpWebRequest.Referer = GetBlogLink(user.username,BlogType.User);

            HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
            //получаем ответ
            string result = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding).ReadToEnd();
            myHttpWebResponse.Close();
            //chal
            if (myHttpWebResponse.StatusCode != HttpStatusCode.OK) return false;
            //должны были получить ответ с нужными куками
            CookieParse(myHttpWebResponse.Headers[HttpResponseHeader.SetCookie]);
            Log("LogInto " + user + " gains cookie = " + sCookie);
            ///
            _isLogedIn = sCookie.Contains("startqip_uniq") && sCookie.Contains("PHPSESSID");
            if (_isLogedIn)
            {
                LogedInAs = user;
            }
            return _isLogedIn;
        }
        */
        #endregion

        #endregion

        #region interface: IFriends

        /// <summary>
        /// Разбор данных об одном аккаунте
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private QipAccaunt ParseString(string data)
        {
            if(String.IsNullOrEmpty(data)) throw new ArgumentNullException("data");
            string[] items = data.Split(',');
            QipAccaunt acc = new QipAccaunt();
            foreach (string item in items)
            {
                string[] parts = item.Trim('"').Split(new[] {"\":\""},
                                                      StringSplitOptions.RemoveEmptyEntries);
                switch (parts[0])
                {
                    case "id":
                        acc.id = int.Parse(parts[1]);
                        break;
                    case "login":
                        acc.login = parts[1];
                        break;
                    case "name":
                        acc.name = parts[1];
                        break;
                    case "avatar":
                        acc.avatar = parts[1];
                        break;
                    case "gender":
                        acc.gender = parts[1];
                        break;
                }
            }
            return acc;
        }

        /// <summary>
        /// Составление списка тех, кто нас читает
        /// </summary>
        /// <param name="userName">имя пользователя</param>
        /// <returns></returns>
        private List<QipAccaunt> GetFollowers(String userName)
        {
            List < QipAccaunt > oListRet = new List<QipAccaunt>(0);
            string oRet = CallApiFunction("info.followers", "or user", userName);
            if(!String.IsNullOrEmpty(oRet))
            {
                MatchCollection mc = Regex.Matches(oRet, @"{([^{}]+)}", Regexoptions);
                if(mc.Count>0)
                {
                    foreach (Match m in mc)
                    {
                        oListRet.Add(ParseString(m.Groups[1].Value));
                    }
                }
            }
            return oListRet;
        }

        /// <summary>
        /// Определяем сколько френдов из списка есть в наших друзьях.
        /// Для сообщества - сколько людей из списка могут писать в сообщество
        /// </summary>
        /// <param name="userName">имя блога или сообщества</param>
        /// <param name="bt">Блог или Сообщество</param>
        /// <param name="friends">список друзей</param>
        /// <returns>Список тех, что читает блог или является членами сообщества</returns>
        public override List<string> IsFriended(String userName, BlogType bt, params string[] friends)
        {
            List<string> myfriends = new List<string>(0);
            try
            {
                if (FriendList == null)//не подгрузили , так загрузим
                {
                    FriendList = GetFollowers(userName);
                }
                foreach (QipAccaunt acc in FriendList)
                {
                    foreach (String friend in friends)
                    {
                        if (String.Compare(friend, acc.login, true) == 0)
                        {
                            myfriends.Add(friend);
                        }
                    }
                }
            }
            catch (Exception e1)
            {
                Log(e1.Message);
            }
            return myfriends;
        }

        
        /// <summary>
        /// Интерфейс по добавлению френдов
        /// </summary>
        /// <param name="who">Имя аккаунта для которого меняем френдов</param>
        /// <param name="friends">Набор имен аккаунтов для френдования</param>
        /// <returns>NULL, если все хорошо, иначе ошибку ввиде Exception</returns>
        public override List<String> AddFriends(UserAccount who, params string[] friends)
        {
            List<String> oRet = new List<string>(0);
            try
            {
                if(LoggedInAs == null || LoggedInAs!=who)
                {
                    if(!LogInto(who))
                    {
                        throw new Exception("Cant login!");
                    }
                }
                for (int i = 0; i < friends.Length; i++)
                {
                    try
                    {
                        string sRet = CallApiFunction("info.show", "or user", friends[i]);
                        QipAccaunt acc = ParseString(Regex.Match(sRet, @"{([^{}]+)}", Regexoptions).Groups[1].Value);
                        if (acc.id>0)
                        {
                            //определяем FollowerID по username
                            sRet = CallApiFunction("friendship.create", "Sid", SID, "FriendID", acc.id.ToString());
                            if(sRet.Contains("\"Error\""))
                            {
                                throw new Exception(sRet);
                            }
                            //friends[i]
                        }else
                        {
                            throw new Exception("Cant find FollowerID!");
                        }
                    }
                    catch (Exception e1)
                    {
                        oRet.Add(friends[i] + ": " + e1.Message);
                    }
                    finally
                    {
                        Thread.Sleep(1000);//1sec before we going again!
                    }

                }
            }
            catch (Exception e1)
            {
                oRet.Add(e1.Message);
            }
            return oRet;
        }

        /// <summary>
        /// Интерфейс по удалению френдов
        /// </summary>
        /// <param name="who">Имя аккаунта для которого меняем френдов</param>
        /// <param name="friends">Набор имен аккаунтов для френдования</param>
        /// <returns>NULL, если все хорошо, иначе ошибку ввиде Exception</returns>
        public override List<String> DelFriends(UserAccount who, params string[] friends)
        {
            List<String> oRet = new List<string>(0);
            try
            {
                if (LoggedInAs == null || LoggedInAs != who)
                {
                    if (!LogInto(who))
                    {
                        throw new Exception("Cant login!");
                    }
                }
                for (int i = 0; i < friends.Length; i++)
                {
                    try
                    {
                        string sRet = CallApiFunction("info.show", "or user", friends[i]);
                        QipAccaunt acc = ParseString(Regex.Match(sRet, @"{([^{}]+)}", Regexoptions).Groups[1].Value);
                        if (acc.id > 0)
                        {
                            //определяем FollowerID по username
                            sRet = CallApiFunction("friendship.destroy", "Sid", SID, "FriendID", acc.id.ToString());
                            if (sRet.Contains("\"Error\""))
                            {
                                throw new Exception(sRet);
                            }
                            //friends[i]
                        }
                        else
                        {
                            throw new Exception("Cant find FollowerID!");
                        }
                    }
                    catch (Exception e1)
                    {
                        oRet.Add(friends[i] + ": " + e1.Message);
                    }
                    finally
                    {
                        Thread.Sleep(1000);//1sec before we going again!
                    }

                }
            }
            catch (Exception e1)
            {
                oRet.Add(e1.Message);
            }
            return oRet;
        }

        /// <summary>
        /// Функция вступления в сообщество.
        /// </summary>
        /// <param name="who">кто вступает</param>
        /// <param name="communityName">куда вступает</param>
        /// <returns>Ошибку, если таковая имела место</returns>
        public Exception JoinCommunnity(UserAccount who, string communityName)
        {
            return new NotImplementedException();
        }

        /// <summary>
        /// Функция покидания сообщества.
        /// </summary>
        /// <param name="who">кто покидает</param>
        /// <param name="communityName">что покидает</param>
        /// <returns>Ошибку, если таковая имела место</returns>
        public Exception LeaveCommunnity(UserAccount who, string communityName)
        {
            return new NotImplementedException();
        }

        #endregion
    }
}